<?php
	/**
	 * manager.php
	 * 
	 * unit for working with files and folders
	 * is called mostly asynchronously via ajaxapi.js
	 * to perform file/folder tasks
	 * 
	 * the functions below are called automatically depending on the sent parameters
	 * 
	 * Most links passed to this unit are encoded in base64
	 * if an argument is not encoded in base64 it is said so in the function documentation
	 * 
	 * @author Emirald Mateli
	 * @package BitManager
     * @todo bug fixing, as always
     * @todo make every function return JSON through succeed/fail functions
	 */
	 
	session_start();
	
	require_once 'bmconfig.php';
	
	require_once 'lib/dir.php';
	require_once 'lib/file.php';
	require_once 'lib/common.php';
	
	
	// Parsing the arguments and determine which function to call
	if (!isParamSet('get', 'action'))
	{
		echo "Invalid number of parameters";
		Exit;
	}
	
		
	switch (strtolower($_GET["action"]))
	{   
		// folder opreations
		case "mkdir"        : createDir();          break;
		case "rmdir"        : deleteDirectory();    break;
		case "dldir"        : downloadDirectory();  break;    
			  
		// file operations
		case "rmfile"       : deleteFile();         break;
		case "mkfile"       : createFile();         break;
		case "savefile"     : saveFile();           break;
		case "download"     : download();           break;
		case "dlthis"       : downloadSelected();   break;
		case "delthis"      : deleteSelected();     break;
		case "copy"         : copyToClipboard();    break;
		case "cut"          : cutToClipboard();     break;
		case "paste"        : pasteFromClipboard(); break;
		case "checkovr"     : pasteNeedsOvr();      break;
		case "renamefile"   : renameFile();         break;
				
		default : echo "Invalid arguments";
	}
	
	#-----------------------------------#
	#   H E L P E R   F U N C T I O N S #
	#-----------------------------------#
	
	/**
	 * Terminates the execution of the script and
	 * Sends a JSObject with message $msg and success = true
	 * 
	 * @param string $msg
	 * @return bool
	 */
	function succeed($msg)
	{
		$arr = array
		(
			"success" => "true",
			"message" => $msg    
		);
	
		return die(json_encode($arr));
	}
	
	/**
	 * Terminates the execution of the script and
	 * Sends a JSObject with message $msg and success = false
	 * 
	 * @param string $msg
	 * @return bool
	 */
	function fail($msg)
	{
		$arr = array
		(
			"success" => "",    // Left intentionally blank, so it will evaluate to false
			"message" => $msg    
		);
	
		return die(json_encode($arr));
	}

	/**
	 * Checks wether a parameter is set and has a value
	 * 
	 * @param string $paramKind : where to check for the parameter. must be either "post" or "get"
	 * @param string $param : the parameter to check
	 */
	function isParamSet($paramKind, $param)
	{
		if (strtolower($paramKind) == "get")
			return isset($_GET[$param]) && ($_GET[$param] !== "");    
		else
			return isset($_POST[$param]) && ($_POST[$param] !== "");  
	}
	
	/**
	 * returns the file being passed in the
	 * get[f] parameter. most of the time files passed to
	 * this file are encoded in base64
	 * 
	 * @param encoded determines wether the parameter is
	 * encoded in base64 or not 
	 * @return the actual value of the parameter
	 */
	function fileFromRequest($encoded = true)
	{
		$filename =  $encoded ? base64_decode($_GET["f"]) : $_GET["f"];
		
		return gpcSlashes($filename);
	}
	
	function fileFromPostRequest($encoded = true)
	{
		$filename =  $encoded ? base64_decode($_POST["f"]) : $_POST["f"];
		
		return gpcSlashes($filename);
	}
	
	/**
	 * Checks if the supplied file/folder name is valid
	 * echoes "Invalid filename" and halts script execution
	 * in case of an invalid filename.
	 */
	function checkFilename($s)
	{
		$file = new File($s);
		
		isValidFilename($file->getFilename()) or exit("Invalid filename");
	}
	
	
	/**
	 * Returns an array with the file names sent to this unit
	 */
	function filesFromRequest()
	{
		# Files from request will return a string
		# with '*' separated base64 encoded strings
		/*$files = fileFromRequest(false);
		
		if ($files == "")
			return array();
			
		$files = explode('*', $files);
		
		for($i = 0; $i < count($files); $i++)
            if ($files[$i] <> '')
			    $files[$i] = base64_decode($files[$i]);
			
		return $files;
        */

        $files = fileFromRequest(false);
        
        if ($files == '')
            return array();
        
        $result = array();
        $files = explode('*', $files);
        
        foreach($files as $file)
            if ($file <> '')
                $result[] = base64_decode($file);
                
        return $result;
	}
	
	/**
	 * Returns an array of filenames that are
	 * into the 'clipboard'. Set by cut/copy operations
	 */
	function filesFromClipboard()
	{
		return isset($_SESSION[BM_CLIPBOARD]) ? $_SESSION[BM_CLIPBOARD] : array();
	}
	
	/**
	 * returns the folder being passed in the
	 * get[p] parameter. most of the time folders passed to
	 * this file are encoded in base64
	 * 
	 * @param encoded (optional/true) determines wether the parameter is
	 * encoded in base64 or not
	 * @return the actual value of the parameter
	 */  
	function folderFromRequest($encoded = true)
	{
		return $encoded ? base64_decode($_GET["p"]) : gpcSlashes($_GET["p"]);
	}
	
	/**
	 * Checks wether the file/folder passed in argument
	 * should be allowed to be manipulated
	 * 
	 * @param string $f the file/folder to check
	 * @return bool True to allow manipulation, false do not allow
	 */
	function isManipulationAllowed($f)
	{
        // Since the requested file/folder doesn't exist yet
        // permissions are checked for both
        return isFileViewAllowed($f) || isAllowedView($f);
	}
	
	/**
	 * Checks if the file/folder passed in the arguments
	 * is allowed to be accessed in any way.
	 * If not it echoes a message and halts script execution
	 */
	function checkPermissions($f)
	{
        if ($f == "")
            return;
        
		// In case the passed argument is an array
		// recursively call this function for each of it's elements
		// and FAIL if a SINGLE ONE is out of user's permissions
		if (is_array($f))
			foreach($f as $arrItem)
				checkPermissions($arrItem);
		elseif (!isManipulationAllowed($f))
			fail("You don't have permission to access '$f' on this server");
	}
	
	#-----------------------------------#
	#  F O L D E R   F U N C T I O N S  #
	#-----------------------------------#
	

	/**
	 * Creates the path sent via the parameters
	 * 
	 * Requires :
	 *   GET : 
	 *     p = path to create
	 * 
	 *  parameter must not be encoded in base64
	 */
	function createDir()
	{
		if (!isParamSet('get', 'p'))
			fail('Invalid number of parameters');    
		
		// Creating the full path of the new directory
		$folder = folderFromRequest(false);      

		@mkdir($folder) ?
			succeed('Directory sucessfully created'):   
			fail("Unable to create directory");

	}

	/**
	 * Deletes the directory passed in the parameter
	 * 
	 * Requires :
	 *   Get :
	 *     p = directory to delete, will fail if not empty
	 *     subs = if 1 will also delete subdirectories
	 * 
	 *     p is not encoded in base64
	 */
	function deleteDirectory()
	{
		if ((!isParamSet('get', 'p')) || (!isParamSet('get', 'subs')))    
		{
			fail("Invalid number of arguments");
			return;
		}      
		
		$folder = folderFromRequest();
		checkPermissions($folder);
		
		$d = new Dir($folder);
		
		if (!$d->exists())
			fail("Requested directory '$folder' doesn't exist");
		
		// Subs=1 indicates subdirectories should be removed aswell
		if ($_GET["subs"] == 1)
		{
			@$d->delete(true) ?
				succeed("Directory and subdirectories sucessfully deleted"):
				fail("Unable to remove directory");
		}
		else
		{
			if (!$d->isEmpty())
			{
				@$d->delete() ?
					succeed("Directory sucessfully deleted"):
					fail("Unable to remove directory");     
			}
			else
				fail("Unable to remove directory. Directory is not empty");
		}    
	}
	
	/**
	 * Downloads the whole directory with subfolders
	 * 
	 * Requires :
	 *    GET:
	 *      p - the folder to download
	 */
	function downloadDirectory()
	{
		isParamSet('get', 'p') or exit('Invalid number of arguments');
		
		$dir = new Dir(folderFromRequest());
		
		// In case the directory doesn't exist
		// there is no need to create the archive
		$dir->exists() or exit('Requested directory doesn\'t exist');
		
		checkPermissions($dir->getFullPath());
		
		// The same when the directory has no files
		count($dir->enumFiles()) or exit('Requested directory does not contain any files');
		
		$za = new ZipArchive(); 
		
		// Archive name is going to be the folder name
		$zipFn = $dir->getName() . '.zip';
		
		$f = new File("");  
		
		if ($za->open($zipFn, ZipArchive::CREATE) === true)
		{      
			foreach ($dir->enumFiles(true) as $file)
			{
				if (!is_file($file))
					continue;
					
				$f->setFullFilename($file);
				
				$za->addFile($file);
			}
			
			$za->close();
			
			if ($f->exists())
			{
				$f->setFullFilename($zipFn);
				$f->download();
			
				unlink($zipFn);
			}
			else
				echo 'Requested directory does not contain any files';  
		}   
	}
	
	#-----------------------------------#
	#    F I L E   F U N C T I O N S    #
	#-----------------------------------#
	
	/**
	 * Creates a file with the file name specified
	 * in the parameter on the active working directory
	 * 
	 * Requires :
	 *   GET :
	 *     f = name of the file to create
	 */
	
	function createFile()
	{
		if (!isParamSet('get', 'f'))
			fail("Invalid number of parameters");

		$f = new File(fileFromRequest(false));
		
		checkFilename($f->getFilename()); 
		checkPermissions($f->getFullFilename());
		
		if (@$f->exists())
			fail("File already exists");
		
		@$f->setContents('') ?
			succeed("File sucessfully created"):
			fail("Unable to create file");
	}
	
	/**
	 * Deletes the the file passed in the parameter
	 * 
	 * Requires :
	 *   GET :
	 *     f = file to delete
	 */
	function deleteFile()
	{
		if (!isParamSet('get', 'f'))
		{
			echo "Invalid number of parameters"; 
			return;  
		}
		
		$f = fileFromRequest();
		
		checkFilename($f);  
		checkPermissions($f);
		
		if (!file_exists($f))
			fail("Requested file '$f' doesn't exist");
		
		if (!isManipulationAllowed($f))
			fail("You don't have permission to modify '$f' on this server");
		
		@unlink($f) ? succeed("File sucessfully deleted") : fail("Unable to delete file");    
	}
	
	/**
	 * Sets the contents of the file passed in the parameter to those passed in "contents" parameter
	 * 
	 * Requires :
	 *   GET : 
	 *     f = the file which the contents will be changed
	 * 
	 *   POST :
	 *    contents = the new contents of the target file
	 */
	function saveFile()
	{    
		if ( (!isParamSet('post', 'f')) || (!isParamSet('post', 'contents')) )
		{
			echo "Invalid number of parameters";
			return;
		}    
		
		if (get_magic_quotes_gpc())
			# Removing slashes added from magic_quotes_gpc()
			$newContents = stripslashes($_POST["contents"]);
		else
			$newContents = $_POST["contents"];
		
		$f = new File(fileFromPostRequest());
		checkPermissions($f->getFullFilename());
		
		if (!$f->exists())
		{
			echo "Requested file doesn't exist";
			return;
		}

		if ($f->setContents($newContents))
			$msg = "File successfully saved";
		else
			$msg = "Unable to save file";
            
        $links[0]['href'] = 'javascript:history.go(-1)';
        $links[0]['text'] = 'Continue Editing';
        $links[1]['href'] = '.?v=' . urlencode($f->getPath());
        $links[1]['text'] = 'Return to directory';
            
        quitWithInfo('File save', $msg, $links);
	}
	
	/**
	 * Downloads the file passed in the get parameter
	 * 
	 * Requires :
	 *   GET : 
	 *     f = the file which will be downloaded
	 */
	function download()
	{
		if (!isParamSet('get', 'f'))
		{
			echo "Invalid number of parameters";
			return;
		}

		$f = new File(fileFromRequest());
		checkPermissions($f->getFullFilename);

		if (isParamSet('get', 'zip') && ($_GET["zip"] == "true"))
			$f->downloadAsZip();
		else
			$f->download();
	}
	
	/**
	 * Downloads the files passed in the get parameter
	 * 
	 * Requires :
	 *   GET : 
	 *     f = the files which will be downloaded
	 */
	function downloadSelected()
	{
		if (!isParamSet('get', 'f'))
		{
			echo "Invalid number of parameters";
			return;
		}
		
		$files = filesFromRequest();
		
		$zipFn = "CustomDownload.zip";
		$za = new ZipArchive();
		
		if ($za->open($zipFn, ZipArchive::CREATE) === true)
		{      
			for($i = 0; $i < count($files); $i++)
			{
				if (!is_file($files[$i]))
					continue;

				# If we have access to this file
				# only then add it to the zip archive
				if (isManipulationAllowed($files[$i]))
                {
                    $f = new File($files[$i]);
                    
                    $za->addFile($files[$i], $f->getFilename());    
                }
					
			}
			
			$za->close();
			
			
			$f = new File($zipFn);
			
			if ($f->exists())
			{
				$f->setFullFilename($zipFn);
				$f->download();
			
				unlink($zipFn);
			}
		}        
	}
	
	/**
	 * Downloads the files passed in the get parameter
	 * 
	 * Requires :
	 *   GET : 
	 *     f = the files which will be downloaded
	 */
	function deleteSelected()
	{
		if (!isParamSet('get', 'f'))
			fail("Invalid number of parameters");
			
		$files = filesFromRequest();     
		
		checkPermissions($files);
		
		foreach ($files as $file) 
		{    
			if (($file == "") || (!file_exists($file)))
				continue;
				
		
			if (!@unlink($file))
				fail("Unable to delete $file");
		} 
		
		succeed("Files sucessfully deleted");
	}
	
	
	/**
	 * Emulates the cut operation by storing the
	 * sent file names in the session cookie
	 * 
	 */
	function copyToClipboard()
	{
		if (!isParamSet('get', 'f'))
			die("Invalid number of parameters");
		
		$files = filesFromRequest();

		checkPermissions($files);
	
		$_SESSION[BM_CLIPBOARD] = $files;
		$_SESSION[BM_COPY] = true;
		
		echo "File(s) copied to clipboard";
	}
	
	/**
	 * Emulates the copy operation by storing the
	 * sent file names in the session cookie
	 * 
	 */
	function cutToClipboard()
	{
		if (!isParamSet('get', 'f'))
		{
			echo "Invalid number of parameters";
			return;
		}
		
		$files = filesFromRequest();
		checkPermissions($files);
	
		$_SESSION[BM_CLIPBOARD] = $files;
		$_SESSION[BM_COPY] = false;
	
        $count = count($files);
    
		printf("%d file%s cut to clipboard", $count, $count == 1 ? '' : 's');
	}
	
	/**
	 * Emulates the paste operation. Copies or moves
	 * files that are stored in the session cookie
	 * 
	 */
	function pasteFromClipboard()
	{
		if (!isParamSet('get', 'p'))
		{
			echo "Invalid number of parameters";
			return;
		}
		
		
		# Directory in which contents are to be pasted
		$dir = folderFromRequest();
		
		# Checking if the user is allowed to write on this directory
		checkPermissions($dir);
		
		# Files which will be 'pasted'
		$files = filesFromClipboard();
		
		checkPermissions($files);
		
		if ((!is_array($files)) || (count($files) == 0))
		{
			echo "Nothing to paste";
			return;
		}
		
		# Overwrite existing files ?
		$ovr = (isset($_GET["ovr"]) && ($_GET["ovr"] == "1"));
		
		$f = new File("");
		
		foreach($files as $file)
		{
			$f->setFullFilename($file);
			
			# In case the source file doesn't exist
			# pick the next one
			if (!$f->exists())
				continue;
			
			# Adjusting the file name to match the new one
			$f->setFullFilename("$dir/{$f->getFilename()}");
			
			# In case an overwriting is needed and the target file exists,
			# delete the target file
			if (($ovr) && ($f->exists()))
				$f->delete();
				
			if ($_SESSION[BM_COPY])
			{
				if (!@copy($file, $f->getFullFilename()))
				{
					echo "Unable to copy file '$file'";
					return;
				}
			}
			else
			{        
				# Rename is also able to move the file
				
				# This is the 'cut' operation
				
				# In case the target file exists but it should
				# not overriden, go to the next file
				if ((!$ovr) && $f->exists())
					continue;
				
				# In case something goes wrong
				# operation should be aborted
				if (!@rename($file, $f->getFullFilename()))
				{
					echo "Unable to move file '$file'";
					return;
				}        
			}
		}
		
		# If the files are successfully pasted
		# Clear the session data
		unset($_SESSION[BM_CLIPBOARD]);
		unset($_SESSION[BM_COPY]);
		
		printf("File%s sucessfully pasted", count($files) == 1 ? '' : 's');
	}
	
	/**
	 * Called before pasteFromClipboard, this function
	 * determines wether to prompt the user to overwrite any files or not
	 * 
	 * this function will output 'true' in case there is some file which
	 * will be overwritten from the paste operation.
	 * 
	 * Requires :
	 *    GET :
	 *      p - the folder in which contents will be pasted
	 * 
	 */
	function pasteNeedsOvr()
	{
		if (!isParamSet('get', 'p'))
		{
			echo "Invalid number of parameters";
			return;
		}
		
		
		$dir = folderFromRequest();
		$files = filesFromClipboard();
		
		if ((!is_array($files)) || (count($files) == 0))
		{
			echo "Nothing to paste";
			return;
		}
		
		$f = new File("");
		
		foreach($files as $file)
		{    
			$f->setFullFilename($file);
			$f->setFullFilename("$dir/{$f->getFilename()}");
			
			if ($f->exists())
			{
				echo "true";
				return;
			}
		}
		
		echo "false";
	}
	
	/**
	 * 
	 * Requires :
	 *    GET :
	 *      f       - the file which will be renamed
	 *      rento   - the new filename 
	 */
	function renameFile()
	{
		if (!( isParamSet('get', 'f') && isParamSet('get', 'rento') ))
		{
			echo "Invalid number of parameters";
			return;
		}
		
		$f = fileFromRequest();
		$newfn = gpcSlashes($_GET["rento"]);
		
		$file = new File($f);              
		
		if ($file->getFilename() == $newfn)
		{
			echo "Nothing to rename";
			return;
		}
		
		checkFilename($newfn);    
		
		if (@$file->ren($newfn))
			echo "File Successfully renamed";
		else
			echo "Unable to rename file";
	}
?>